/*
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
#include <comic.h>
#include <Ecore_Evas.h>
#include <Ecore.h>
#include <Edje.h>
#include <Eet.h>
#include <Evas.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <sys/stat.h>
#include <comic_scrolling.h>



#include <debug.h>

#define BUFSIZE 1024
#define GAP 10

extern Ecore_Evas * ee;
extern Evas * evas;
extern struct comic_reader_guis *comic;

int WIDTH()  {
	int __width;
	evas_output_size_get(ecore_evas_get(ee), &__width, NULL);
	return __width;
}
int HEIGHT()  {
	int __height;
	evas_output_size_get(ecore_evas_get(ee), NULL, &__height);
	return __height;
}

int VWIDTH() {
	int __width;
	Evas_Object *obj;
	obj = (Evas_Object *) edje_object_part_object_get(comic->background, "viewport");
	evas_object_geometry_get(obj, NULL, NULL, &__width, NULL);
	return __width;
}

int VHEIGHT() {
	int __height;
	Evas_Object *obj;
	obj = (Evas_Object *) edje_object_part_object_get(comic->background, "viewport");
	evas_object_geometry_get(obj, NULL, NULL, NULL, &__height);
	return __height;
}

static void comic_book_pages_beside_align(struct comic_book *book);
void  comic_book_page_scale_set(struct comic_page *page, int scale);
static void comic_book_page_info_string_update();

static int __animator_square_move_pages(void *object_shift);
Evas_Object * comic_edje_object_get(const char *name) {
	char buf[64];
	Evas_Object *obj;
	snprintf(buf, sizeof(buf), "%s/comic_reader.edj", THEME_PATH);
	if (!evas) {
		error("Evas is NULL!!\n");
		return NULL;
	}
	if (!name) {
		error("Name is NULL, what object you want to get??\n");
		return NULL;
	}
	obj = edje_object_add(evas);
	edje_object_file_set(obj, buf, name);
	return obj;
}


struct comic_reader_config * comic_read_config_new() {
	struct comic_reader_config * ans = (struct comic_reader_config *)malloc (sizeof(struct comic_reader_config));
	ans->reading_seq = RIGHT_TO_LEFT;
	return ans;
}

static int __strcompare(const void *p1, const void *p2) {
	return strcmp((char *) p1, (char *) p2);
}

int strcompare(const void *p1, const void *p2) {
	return strcmp(* (char * const *) p1, * (char * const *) p2);
}


Evas_List *comic_book_list(Evas_List *list, const char *directory) {
	DIR *dir;
	struct dirent *dentry;
	char filename[BUFSIZE];
	struct stat s;
	dir = opendir(directory);
	if (!dir)  {
		error("Failed to open dir %s\n", directory);
		return NULL;
	}
	while ((dentry = readdir(dir))) {
		if (dentry->d_name[0] == '.')
			continue;
		snprintf(filename, sizeof(filename), "%s/%s", directory, dentry->d_name);
		if (stat(filename, &s) < 0) {
			error("stat %s failed !!\n", filename);
			continue;
		}
		if (S_ISDIR(s.st_mode))
			list = comic_book_list(list, filename);
		else if (strstr(filename, ".eet")) {
			list = evas_list_append(list, strdup(filename));
		}
	}
	closedir(dir);
	evas_list_sort(list, evas_list_count(list), &__strcompare);

	return list;
}

void debug_show_string_list(Evas_List *list) {
	int i;
	char *str;
	if (!list) return;
	for (i = 0;i < evas_list_count(list);i++) {
		str = (char *)evas_list_nth(list, i);
		debug("list[%d]==%s\n", i, str);
	}
}



struct comic_book * comic_book_new(const char *book) {
	struct comic_book * ans;
	Eet_File *ef;
	char **list;
	int i, num;

	if (!book) return NULL;
	ef = eet_open(book, EET_FILE_MODE_READ);
	if (!ef) {
		error("Bad book %s!!\n", book);
		return NULL;
	}
	ans = (struct comic_book *)malloc(sizeof(struct comic_book));
	memset(ans, 0, sizeof(struct comic_book));
	ans->name = strdup(book);
	list = eet_list(ef, "*", &num);
	qsort(list, num, sizeof(char *), strcompare);
	for (i = 0;i < num;i++) {
		ans->keys = evas_list_append(ans->keys, strdup(list[i]));
	}
	ans->index = -1;
	ans->scale = 0;
	ans->zoom = comic_edje_object_get("comic/zoom");
	evas_object_move(ans->zoom, 0, VHEIGHT() - ZOOM_SIZE);
	evas_object_resize(ans->zoom, ZOOM_SIZE, ZOOM_SIZE);
	evas_object_event_callback_add(ans->zoom,
	                               EVAS_CALLBACK_MOUSE_DOWN,
	                               &zoom_mouse_down_cb,
	                               ans);
	evas_object_event_callback_add(ans->zoom,
	                               EVAS_CALLBACK_MOUSE_MOVE,
	                               &zoom_mouse_move_cb,
	                               ans);
	evas_object_event_callback_add(ans->zoom,
	                               EVAS_CALLBACK_MOUSE_UP,
	                               &zoom_mouse_up_cb,
	                               ans);

	evas_object_show(ans->zoom);
	ans->ef = ef;
	return ans;
}

void _comic_book_clean_pages(struct comic_book *comic) {
	if (!comic) return;
	if (comic->last_page) {
		evas_object_del(comic->last_page->page);
		free(comic->last_page);
		comic->last_page = NULL;
	}
	if (comic->current_page) {
		evas_object_del(comic->current_page->page);
		free(comic->current_page);
		comic->current_page = NULL;
	}
	if (comic->next_page) {
		evas_object_del(comic->next_page->page);
		free(comic->next_page);
		comic->next_page = NULL;
	}
}

void comic_book_free(struct comic_book *comic) {
	char *key;
	if (!comic) return;
	_comic_book_clean_pages(comic);
	free(comic->name);
	comic->name = NULL;
	while (evas_list_count(comic->keys)) {
		key = evas_list_nth(comic->keys, 0);
		comic->keys = evas_list_remove(comic->keys, key);
		free(key);
		key = NULL;
	}
	evas_list_free(comic->keys);
	evas_object_del(comic->zoom);
	comic->keys = NULL;
	eet_close(comic->ef);
	free(comic);
}

void comic_book_goto_index(struct comic_book *book, int index) {
	char *key;
	if (!comic) return;
	if (!book) return;
	_comic_book_clean_pages(book);
	if (index < 0 || index > evas_list_count (book->keys)) {
		error("index %d is out of range!!\n", index);
		return ;
	}
	key = evas_list_nth(book->keys, index);
	book->current_page = comic_book_page_get_key(book, key);
	if ((index - 1) >= 0) {
		book->last_page = comic_book_page_get_key(book, evas_list_nth(book->keys, index - 1));
	}
	if ((index + 1) < evas_list_count (book->keys)) {
		book->next_page = comic_book_page_get_key(book, evas_list_nth(book->keys, index + 1));
	}
	book->index = index;
	comic_book_pages_align(book, comic->config);
	comic_book_page_info_string_update();
}

struct comic_page *comic_book_page_get_key(struct comic_book *book, const char *key) {
	struct comic_page *ans = NULL;
	Evas_Object *img;
	unsigned int w, h;
	int alpha, compress, quality, lossy;
	int ret;
	if (!book) return NULL;
	if (!key) {
		error("Hey key is NULL\n");
		return NULL;
	}
	ans = (struct comic_page *) malloc(sizeof(struct comic_page));
	memset(ans, 0, sizeof(struct comic_page));

	img = evas_object_image_add(evas);
	ret = eet_data_image_header_read(book->ef, key, &w, &h, &alpha, &compress, &quality, &lossy);
	evas_object_image_file_set(img, book->name, key);

	evas_object_clip_set(img, comic->viewport);
	ans->page = img;
	ans->scale = 1.0;
	ans->ow = w * VHEIGHT() / h;
	ans->oh = VHEIGHT();
	evas_object_pass_events_set(ans->page, 1);
	comic_book_page_scale_set(ans, ans->scale);

	debug("key=%s width=%d height=%d\n", key, ans->w, ans->h);

	return ans;
}

static inline double ratio_get(int scale) {
	return (double)(100.0f + scale) / 100.0f;
}


void comic_page_turning_detail_temp(struct comic_page *page, int x_pos, int y_pos) {
	if (!page) return;
	evas_object_resize(page->page, page->w, page->h);
	evas_object_image_fill_set(page->page, 0, 0, page->w, page->h);
	evas_object_move(page->page, x_pos, y_pos);
}

void comic_page_turning_detail(struct comic_page *page, int x_pos, int y_pos) {
	if (!page) return;
	comic_page_turning_detail_temp(page, x_pos, y_pos);
	page->x = x_pos;
	page->y = y_pos;
}

// align 	0  left
// 		1  right
int _comic_x_fix_ (struct comic_page *page, int start, int pic_width, int align, int gap) {
	int ans = start;
	if ((pic_width + gap) <= WIDTH()) {
		ans += (WIDTH() - pic_width) >> 1 ;
		if (page) {
			page->page_x = start;
			page->page_w = WIDTH();
			page->page_h = page->h >= VHEIGHT()? page->h : VHEIGHT();;
		}
		return ans;
	}
	if (align == 0) {  // left align
		ans = start + gap;
	} else {
		ans = start + WIDTH() - pic_width;
	}
	if (page) {
		page->page_w = pic_width + gap;
		page->page_x = ans - gap;
	}
	return ans;
}

static void calculate_page_boundard_size(struct comic_page *page) {
	if (page->w < (WIDTH() - 2*GAP)) {
		page->page_w = WIDTH();
		page->page_x = page->x - (WIDTH() - page->w) / 2;
	} else {
		page->page_w = page->w + 2 * GAP;
		page->page_x = page->x - GAP;
	}
	if (page->h <= (VHEIGHT())) {
		page->page_h = VHEIGHT();
	} else {
		page->page_h = page->h;
	}
	page->page_y = page->y;
}

void  comic_book_page_scale_set(struct comic_page *page, int scale) {
	double rate = ratio_get(scale);
	if (!page) return;
	page->x =  (page->x - (WIDTH() / 2) ) / page->scale * rate + (WIDTH() / 2);
	page->y =  (page->y - (VHEIGHT() / 2) ) / page->scale * rate + (VHEIGHT() / 2);
	page->w = page->ow * rate;
	page->h = page->oh * rate;
	page->y = page->y > 0 ? 0 : page->y;
	page->y = page->y + page->h < VHEIGHT() ? VHEIGHT() - page->h : page->y;

	calculate_page_boundard_size(page);

	evas_object_move(page->page, page->x, page->y);
	evas_object_resize(page->page, page->w, page->h);
	evas_object_image_fill_set(page->page, 0, 0, page->w, page->h);
	debug("Image (%d, %d, %d, %d)\n", page->x, page->y, page->w, page->h);
	debug("Page  (%d, %d, %d, %d)\n", page->page_x, page->page_y, page->page_w, page->page_h);
	page->scale = rate;
}

void comic_book_scale_set(struct comic_book *book, int scale) {
	if (!book) return;
	if (book->scale == scale) return;
	scale = scale <= 0 ? 0 : scale >= 100 ? 100 : scale;
	debug("changing scale to = %d\n", scale);

	comic_book_page_scale_set(book->current_page, scale);
	if (book->next_page) {
		comic_book_page_scale_set(book->next_page, scale);
	}
	if (book->last_page) {
		comic_book_page_scale_set(book->last_page, scale);
	}
	comic_book_pages_beside_align(book);
	book->scale = scale;
}

static void comic_book_pages_beside_align(struct comic_book *book) {
	int x;
	if (comic->config->reading_seq == RIGHT_TO_LEFT) {
		if (book->last_page) {
			x = _comic_x_fix_(book->last_page, WIDTH(), book->last_page->w, 0, GAP);
			comic_page_turning_detail(book->last_page, x, book->last_page->y);
		}
		if (book->next_page) {
			x = _comic_x_fix_(book->next_page, book->current_page->page_x - WIDTH(), book->next_page->w, 1, GAP);
			comic_page_turning_detail(book->next_page, x, book->next_page->y);
		}
	} else {	// LEFT_TO_RIGHT
		if (book->last_page) {
			x = _comic_x_fix_(book->last_page, book->current_page->page_x - WIDTH(), book->last_page->w, 1, GAP);
			comic_page_turning_detail(book->last_page, x, book->last_page->y);
		}
		if (book->next_page) {
			x = _comic_x_fix_(book->next_page, WIDTH(), book->next_page->w, 1, GAP);
			comic_page_turning_detail(book->next_page, x, book->next_page->y);
		}
	}

}

// Use only when moving the page to the initial point
void comic_book_pages_align(struct comic_book *book, struct comic_reader_config *config) {
	int x;
	const int y = 0;
	comic_book_page_scale_set(book->last_page, book->scale);
	comic_book_page_scale_set(book->current_page, book->scale);
	comic_book_page_scale_set(book->next_page, book->scale);

	x = _comic_x_fix_(book->current_page, 0, book->current_page->w, 1, GAP);
	comic_page_turning_detail(book->current_page, x, y);

	comic_book_pages_beside_align (book);

	evas_object_show(book->current_page->page);
	if (book->last_page)
		evas_object_show(book->last_page->page);
	if (book->next_page)
		evas_object_show(book->next_page->page);
	evas_object_raise(book->zoom);
}

void comic_book_move_to_next(struct comic_book *book) {
	int next_x;
	int xdiff, ydiff;
	struct comic_page *next_page = NULL;
	if (!book) {
		error("Hey book is NULL\n");
		return;
	}
	if (!book->next_page) {
		return;
	}
	comic_book_page_scale_set(book->next_page, book->scale);
	next_x = _comic_x_fix_(NULL, 0,  book->next_page->w, 1, GAP);
	xdiff = next_x - book->next_page->x;
	ydiff = 0 - book->next_page->y;  // y should be added

	if ( (book->index + 2) < evas_list_count (book->keys)) {
		next_page = comic_book_page_get_key(book, evas_list_nth(book->keys, book->index + 2));
	}

	animator_move_object(book->next_page->page,
	                     &__animator_square_move_pages,
	                     ANIMATOR_MAGIC,
	                     book->next_page->x,
	                     book->next_page->y,
	                     next_x,
	                     0);
	animator_move_object(book->current_page->page,
	                     &__animator_square_move_pages,
	                     ANIMATOR_MAGIC,
	                     book->current_page->x,
	                     book->current_page->y,
	                     book->current_page->x + xdiff,
	                     book->current_page->y + ydiff);
	if (book->last_page) {
		evas_object_del(book->last_page->page);
		free(book->last_page);
		book->last_page = NULL;
	}
	book->last_page = book->current_page;
	book->current_page = book->next_page;
	book->index++;
	book->next_page = next_page;
	comic_book_page_info_string_update();
}

void comic_book_move_to_last(struct comic_book *book) {
	int next_x;
	int xdiff, ydiff;
	struct comic_page *last_page = NULL;
	if (!book) {
		error("Hey book is NULL\n");
		return;
	}
	if (!book->last_page) {
		return;
	}
	comic_book_page_scale_set(book->last_page, book->scale);
	next_x = _comic_x_fix_(NULL, 0,  book->last_page->w, 1, GAP);
	xdiff = next_x - book->last_page->x;
	ydiff = 0 - book->last_page->y;  // y should be added
	if (book->index - 2 >= 0) {
		last_page = comic_book_page_get_key(book, evas_list_nth(book->keys, book->index - 2));
	}

	animator_move_object(book->last_page->page, &__animator_square_move_pages,
	                     ANIMATOR_MAGIC,
	                     book->last_page->x,
	                     book->last_page->y,
	                     next_x,
	                     0);
	animator_move_object(book->current_page->page, &__animator_square_move_pages,
	                     ANIMATOR_MAGIC,
	                     book->current_page->x,
	                     book->current_page->y,
	                     book->current_page->x + xdiff,
	                     book->current_page->y + ydiff);
	if (book->next_page) {
		evas_object_del(book->next_page->page);
		free(book->next_page);
		book->next_page = NULL;
	}
	book->next_page = book->current_page;
	book->current_page = book->last_page;
	book->index--;
	book->last_page = last_page;
	comic_book_page_info_string_update();
}

int animator_square_move_cb(void *object_shift) {
	struct Object_Shift  *OS = (struct Object_Shift  *) object_shift;
	double elapse = ecore_time_get() - OS->start_time;
	double ratio;
	double x, y;
	if (elapse >= OS->finish_time) {
		evas_object_move(OS->obj, OS->e_x, OS->e_y);
		remove_mask(comic);
		if (OS->cb) {
			OS->cb(OS->data);
		}
		free(OS);
		return 0;
	}
	ratio = 1 - (elapse / OS->finish_time);
	ratio = 1 - (ratio * ratio * ratio);
	x = OS->s_x + ratio * (OS->e_x - OS->s_x);
	y = OS->s_y + ratio * (OS->e_y - OS->s_y);
	evas_object_move(OS->obj, x, y);
	return 1;
}

static int __animator_square_move_pages(void *object_shift) {
	struct Object_Shift  *OS = (struct Object_Shift  *) object_shift;
	double elapse = ecore_time_get() - OS->start_time;
	double ratio;
	double x, y;
	if (elapse >= OS->finish_time) {
		evas_object_move(OS->obj, OS->e_x, OS->e_y);
		comic_book_pages_align(comic->book, comic->config);
		remove_mask(comic);
		if (OS->cb) {
			OS->cb(OS->data);
		}
		free(OS);
		return 0;
	}
	ratio = 1 - (elapse / OS->finish_time);
	ratio = 1 - (ratio * ratio * ratio);
	x = OS->s_x + ratio * (OS->e_x - OS->s_x);
	y = OS->s_y + ratio * (OS->e_y - OS->s_y);
	evas_object_move(OS->obj, x, y);
	return 1;
}

void animator_move_object_callback(Evas_Object *o, int (*func)(void *), double time, int sx, int sy, int ex, int ey, Callback cb, void *data) {
	struct Object_Shift  *OS;
	if (o == NULL) {
		debug("o is NULL!!\n");
		return;
	}
	if (time <= 0) {
		evas_object_move(o, ex, ey);
		return;
	}
	OS = (struct Object_Shift *) malloc (sizeof(struct Object_Shift));
	memset(OS, 0, sizeof(struct Object_Shift));
	OS->obj = o;
	OS->finish_time = time;
	OS->s_x = sx;
	OS->s_y = sy;
	OS->e_x = ex;
	OS->e_y = ey;
	OS->start_time = ecore_time_get();
	OS->cb = cb;
	OS->data = data;
	put_mask(comic);
	ecore_animator_add(func, OS);
}

void animator_move_object(Evas_Object *o, int (*func)(void *), double time, int sx, int sy, int ex, int ey) {
	animator_move_object_callback(o, func, time, sx, sy, ex, ey, NULL, NULL);
}


struct comic_page *the_page_at_right() {
	if (!comic) return NULL;
	if (!comic->book) return NULL;
	if (comic->config->reading_seq == RIGHT_TO_LEFT) {
		return comic->book->last_page;
	}
	return comic->book->next_page;
}

struct comic_page *the_page_at_left() {
	if (!comic) return NULL;
	if (!comic->book) return NULL;
	if (comic->config->reading_seq == RIGHT_TO_LEFT) {
		return comic->book->next_page;
	}
	return comic->book->last_page;
}

void put_mask(struct comic_reader_guis *comic) {
	if (!comic->mask) {
		comic->mask = comic_edje_object_get("comic/mask");
	}
	evas_object_resize(comic->mask, WIDTH(), HEIGHT());
	evas_object_move(comic->mask, 0, 0);
	evas_object_raise(comic->mask);
	evas_object_show(comic->mask);
}

void remove_mask(struct comic_reader_guis *comic) {
	if (!comic->mask) {
		return;
	}
	evas_object_hide(comic->mask);
}

static void comic_book_page_info_string_update() {
	char buf[1024];
	if (comic->book) {
		snprintf(buf,sizeof(buf),"%d/%d",comic->book->index+1, evas_list_count(comic->book->keys));
	} else {
		snprintf(buf,sizeof(buf),"0/0");
	}
	comic_book_page_info_string_set(buf);
}

void comic_book_page_info_string_set(char *str) {
	if (!str) return;
	edje_object_part_text_set(comic->background, "page", str);
}
